home *** CD-ROM | disk | FTP | other *** search
/ Network Supervisor's Toolkit / Network Supervisor's Toolkit.iso / tools / nwtp06 / blts9401.txt < prev    next >
Text File  |  1996-07-10  |  33KB  |  759 lines

  1. ARTICLES: IPX/SPX for NetBIOS Developers
  2.  
  3. Original article: (c) Copyright Novell, 1994
  4.                   Novell Professional Developer BULLETS
  5.                   January 1994 (Volume 6, Number 1)
  6. NwTP additions  : (between angular brackets/ minus signs [- ... -])
  7.  
  8.      NetBIOS is a popular peer-to-peer communication method that it is
  9.      supported under NetWare through a NetBIOS emulator. However, even though
  10.      NetBIOS is supported, there are definite advantages to using Novell's
  11.      "native tongue" protocols, IPX (Internet Packet eXchange) and SPX
  12.      (Sequenced Packet eXchange), when doing peer-to-peer communication.
  13.  
  14.      This article discusses the advantages of using IPX/SPX and provides an
  15.      introduction to Novell's IPX and SPX protocols for developers who have a
  16.      working familiarity with NetBIOS.
  17.  
  18. Why Use IPX/SPX?
  19.  
  20.      The most obvious reason to use IPX and SPX is to improve performance;
  21.      since NetWare emulates NetBIOS, processing NetBIOS commands involves more
  22.      overhead than processing IPX/SPX commands. NetWare encapsulates emulated
  23.      NetBIOS packets within IPX packets before they go out on the wire, so
  24.      moving to IPX/SPX allows you to "cut out the middleman."
  25.  
  26.      You lose no connectivity by switching protocols either, since the
  27.      emulated NetBIOS layer cannot communicate with hardware NetBIOS systems.
  28.      In fact, moving to IPX/SPX gives you a net gain in connectivity; NetWare
  29.      has a 70% share of the network operating system market.
  30.  
  31.      Also, since the NetBIOS emulator adds an additional layer of complexity
  32.      to packets being sent out, it is more difficult to troubleshoot problems.
  33.      Emulating NetBIOS involves an extra driver and an extra set of potential
  34.      incompatibilities. Generally speaking, since IPX and SPX are not
  35.      dramatically different from NetBIOS, it makes your job easier to work
  36.      with the protocols that NetWare is designed to support.
  37.  
  38. Datagram Services
  39.  
  40.      Novell's IPX protocol provides almost the same functionality as NetBIOS
  41.      datagrams. Both specifications deliver packets on a best-effort basis,
  42.      but with no guarantee of delivery or sequencing. Both IPX and NetBIOS
  43.      also provide the capability to send packets either to a single node or to
  44.      multiple nodes. NetBIOS supports the multicast, or the sending of a
  45.      datagram to a selected group of nodes with the same group name. Since IPX
  46.      is address-based instead of name-based, this capability is not directly
  47.      supported; instead IPX must send an individual packet to each node.
  48.  
  49.      NetBIOS also supports the broadcast datagram, a datagram that is
  50.      broadcast to the entire internetwork. IPX supports broadcasts, but only
  51.      to one subnet at a time. Usually, this restriction poses no problem,
  52.      since mechanisms such as the NetWare Service Advertising Protocol (SAP)
  53.      overcome this limitation.
  54.  
  55.      The data portion of a NetBIOS datagram is limited in length to 512 bytes,
  56.      whereas IPX packets allow 546 bytes of data on all networks and can
  57.      sometimes be substantially larger than that depending on the maximum
  58.      packet size supported by network routers. Some networks can handle packet
  59.      sizes of 4096 bytes or more.
  60.  
  61. Session Services
  62.  
  63.      As in the relationship between IPX and NetBIOS datagrams, Novell's SPX
  64.      protocol serves much the same function as the NetBIOS session. Both SPX
  65.      and NetBIOS sessions provide guaranteed delivery and sequencing of
  66.      packets, but at the cost of increased overhead.
  67.  
  68.      The primary difference between the two is the supported packet size.
  69.      NetBIOS sessions support 64K packet sizes (128K with Chain Sends). SPX
  70.      has the same 546-byte packet size limitation as IPX and, in fact, SPX
  71.      allows slightly less data in a packet than IPX, since the SPX header
  72.      requires an additional 12 bytes. SPX therefore supports 534 bytes of data
  73.      on all networks with the potential for much larger packets if supported
  74.      by the routers, although attaining a 64K packet size is unlikely.
  75.  
  76.      Probably the most noticeable difference between IPX/SPX and NetBIOS is
  77.      how each addresses packets. IPX/SPX addresses packets using network,
  78.      node, and socket numbers. NetBIOS uses unique names to address packets.
  79.      Each workstation can be uniquely addressed using the network and node
  80.      numbers.
  81.  
  82.      A workstation can then have as many open sockets as desired for receiving
  83.      peer-to-peer data packets. Many methods exist for determining a
  84.      workstation's network, node, and destination socket number, but for
  85.      simplicity the example code in this article uses SAP to obtain this
  86.      information.
  87.  
  88. The Waiting Game
  89.  
  90.      With NetBIOS, you can choose to allow most NetBIOS commands to complete
  91.      before returning control to the application, but most IPX/SPX commands
  92.      return control immediately. In other words, most IPX/SPX commands are
  93.      "no-wait" commands; there is no IPX/SPX "wait" counterpart.
  94.  
  95.      Since most NetBIOS developers use the "no-wait" variants, this difference
  96.      should not pose a problem, but if you need to use a "wait," you can code
  97.      it very simply by issuing the command and then looping on the in use
  98.      field.
  99.  
  100. Asynchronous Events
  101.  
  102.      IPX/SPX also has a feature that is not used with NetBIOS: the
  103.      asynchronous event. An asynchronous event can be initiated at any time
  104.      and, as the name implies, can be set to occur independent of an
  105.      application's execution path. An event could be set up, for example, to
  106.      automatically broadcast an IPX packet every 45 seconds. The application
  107.      initiating this event could then continue processing and leave the timing
  108.      and broadcasting of packets to the IPX event handler.
  109.  
  110. The Network Control Block & the Event Control Block
  111.  
  112.      From a developer's perspective, the "core" of NetBIOS is the Network
  113.      Control Block (NCB). IPX and SPX are based on an Event Control Block
  114.      (ECB) and an IPX/SPX header. Figure 1 describes the fields in the ECB.
  115.  
  116.      *********************************************************
  117.      Figure 1: The IPX/SPX Event Control Block  [- C structure -]
  118.  
  119.      void far *linkAddress              Set by IPX
  120.      void (far *ESRAddress)()           Equivalent to NetBIOS POST routine
  121.      BYTE inUseFlag                     Set when the ECB is in use, zero
  122.                                         when it is available
  123.      BYTE completionCode                Equivalent to NetBIOS Command
  124.                                         Completion
  125.      WORD socketNumber                  Socket number associated with ECB
  126.      BYTE IPXWorkspace[4]               Set by IPX
  127.      BYTE driverWorkspace[12]           Set by IPX
  128.      BYTE immediateAddress[6]           Node address of next "hop"
  129.      WORD fragmentCount                 Number of buffer fragments in packet
  130.      ECBFragment fragmentDescriptor[2]  Address and size of fragment(s)
  131.  
  132.      END of FIGURE 1
  133.      *********************************************************
  134.  
  135.  [-  *********************************************************
  136.      Figure 1a: The IPX/SPX Event Control Block (Pascal syntax)
  137.  
  138.      linkAddress     :Pointer                   Set by IPX
  139.      ESRAddress      :Pointer                   Equivalent to NetBIOS
  140.                                                 POST routine
  141.      InUseFlag       :Byte;                     Set when the ECB is in use,
  142.                                                 zero when it is available
  143.      CompletionCode  :Byte;                     Equivalent to NetBIOS Command
  144.                                                 Completion
  145.      SocketNumber    :Word;                     Socket number associated
  146.                                                 with ECB
  147.      IPXWorkspace    :array[1..4] of byte;      Set by IPX
  148.      DriverWorkspace :array[1..12] of byte;     Set by IPX
  149.      ImmediateAddress:array[1..6] of byte;      (Tnodeaddress)
  150.                                                 Node address of next "hop"
  151.      FragmentCount   :word;                     Number of buffer fragments
  152.                                                 in packet
  153.      Fragment        :array[1.. ] of Tfragment  Address and size of
  154.                                                 fragment(s)
  155.  
  156.      (Note: this structure is declared as the Tecb type in the nwIPX unit)
  157.  
  158.      END of FIGURE 1a
  159.      ********************************************************* -]
  160.  
  161.      Note that the ECB contains a field that has no equivalent in the NCB
  162.      called the immediate address field. This field should be populated with
  163.      the node address of the first "hop" on the way to the packet's ultimate
  164.      destination. Novell provides an API call to populate this field, the
  165.      IPXGetLocalTarget() API available in the NetWare Client SDK.
  166.  
  167. IPX Send Example
  168.  
  169.      The sample code in this article includes simple examples written under
  170.      DOS with the NetWare Client SDK. Figure 2 shows a routine sending an IPX
  171.      packet.
  172.  
  173.      *********************************************************
  174.      Figure 2: IPX Send [- C example -]
  175.  
  176.      /* Send "Hello!" to the station at network 0x11111111, node
  177.         0x222222222222, socket 0x3333 using IPX */
  178.  
  179.      void IPXSayHello()
  180.      {
  181.         char buffer[] = "Hello!";
  182.         ECB ecb;
  183.         IPXHeader header;
  184.         int transTime;
  185.         header.packetType = 4;
  186.         memset(header.destination.network, 0x11, 4);
  187.         memset(header.destination.node,    0x22, 6);
  188.         memset(header.destination.socket,  0x33, 2);
  189.  
  190.         ecb.ESRAddress     = NULL;
  191.         ecb.socketNumber   = 0x4444;
  192.         IPXGetLocalTarget(header.destination,
  193.                           ecb.immediateAddress, &transTime);
  194.         ecb.fragmentCount = 2;
  195.         ecb.fragmentDescriptor[0].address = &header;
  196.         ecb.fragmentDescriptor[0].size    = sizeof(IPXHeader);
  197.         ecb.fragmentDescriptor[1].address = buffer;
  198.         ecb.fragmentDescriptor[1].size    = strlen(buffer) + 1;
  199.         IPXSendPacket(&ecb);
  200.      }
  201.  
  202.      END of FIGURE 2
  203.      *********************************************************
  204.  
  205.  [-  *********************************************************
  206.      Figure 2a: IPX Send (Pascal example)
  207.  
  208.      { Send "Hello!" to the station at network $11111111, node
  209.        $222222222222, socket $3333 using IPX }
  210.  
  211.      Procedure IPXSayHello;
  212.      Var buffer:string;
  213.          ecb:Tecb;
  214.          header:TipxHeader;
  215.          transTime:Word;
  216.      begin
  217.       Buffer:="Hello!";
  218.       header.packetType := 4;
  219.       FillChar(header.destination.network,4,$11);
  220.       FillChar(header.destination.node,   6,$22);
  221.       FillChar(header.destination.socket, 2,$33);
  222.  
  223.       ecb.ESRAddress:=NIL;
  224.       ecb.socketNumber:=$4444;
  225.       IPXGetLocalTarget(header.destination,
  226.                         ecb.immediateAddress, transTime);
  227.       ecb.fragmentCount:=2;
  228.       ecb.fragment[1].address:= @header;
  229.       ecb.fragment[1].size   := SizeOf(TIPXHeader);
  230.       ecb.fragment[2].address:= @buffer[1];
  231.       ecb.fragment[2].size:= ord(buffer[0]);
  232.       IPXSendPacket(ecb);
  233.      end;
  234.  
  235.      END of FIGURE 2a
  236.      ********************************************************* -]
  237.  
  238.  
  239.      The first apparent difference between IPX and NetBIOS is that IPX uses
  240.      two buffers where NetBIOS would use one. The first buffer is the IPX
  241.      Header containing the source and destination addresses, the packet type,
  242.      and several "housekeeping" fields. Refer to Figure 3 for a description of
  243.      the IPX header.
  244.  
  245.      *********************************************************
  246.      Figure 3: IPX Header
  247.  
  248.      WORD       checkSum           Included to conform to Xerox IDP standard
  249.                                    Set to FFFF by IPX
  250.      WORD       length             Length of entire IPX packet including
  251.                                    header
  252.                                    Set by IPX
  253.      BYTE       transportControl   Hop count - Set to zero by IPX
  254.      BYTE       packetType         IPX packet type is 4
  255.      IPXAddress destination        Address the packet is sent to
  256.                                    [- Pascal: of type TInternetworkAddress -]
  257.      IPXAddress source             Address of node sending packet set by IPX
  258.                                    [- Pascal: of type TinternetworkAddress -]
  259.  
  260.      END of FIGURE 3
  261.      *********************************************************
  262.  
  263.      The second buffer is the data to be sent. Two fields in the IPX header
  264.      must be set for an IPX send: the packet type and the destination address.
  265.      IPX packets are type 4, SPX packets are type 5. [- Note that according
  266.      to the original xerox definitions this statement is not correct. Type 4
  267.      packets are reserved for the PEP protocol. Use type 0 (undefined) when
  268.      transmitting standard IPX packets-] The destination address consists
  269.      of a four-byte network number, a six-byte node number, and a two-byte
  270.      socket number.
  271.  
  272.      If these examples used an Event Service Routine (ESR), the ESR address
  273.      would be filled with the address of a procedure to be run when the send
  274.      completes, but since NULL is specified, this routine will not be run. The
  275.      ESR is equivalent to the NetBIOS POST routine. When the IPX send
  276.      executes, the rest of the fields in the IPX header are filled in
  277.      automatically, including the source address. You must specify the socket
  278.      number to be included in the source address, but the socket need not be
  279.      open to send a packet. For this example, socket number 0x4444 was
  280.      arbitrarily chosen.
  281.  
  282.      The immediate address field described above must be filled in as well,
  283.      and the IPXGetLocalTarget() API call fills in this field with the
  284.      appropriate value. It is passed the final destination of the packet and
  285.      it calculates the address of the "first hop" on the way to the final
  286.      destination. Note that if the target workstation is on the same subnet as
  287.      the sending workstation the immediate address will be the same as the
  288.      final destination. Otherwise, it will be a bridge or router on the
  289.      subnet.
  290.  
  291.      Each of the buffers sent in the IPX packet is considered to be a
  292.      fragment. Since there are two buffers (the IPX header and the data), the
  293.      fragment count is equal to two. The address and size of the fragments are
  294.      then entered, starting with the IPX header. As soon as all of the
  295.      relevant fields are filled, the example calls IPXSendPacket() and passes
  296.      it the address of the ECB.
  297.  
  298.      Receiving an IPX packet is much like sending one from a programming
  299.      standpoint, except that you do not need to set the IPX header fields. In
  300.      the ECB, you should set the ESR address, socket number, immediate
  301.      address, and fragment descriptors.
  302.  
  303.      Note about socket numbers: the socket number specified for an IPX send
  304.      does not need to be open, but for an IPX receive the socket must be open.
  305.      The API call to receive an IPX packet is IPXListenForPacket().
  306.  
  307. SPX Connection Example
  308.  
  309.      Figure 4 contains a code sample that establishes an SPX connection.
  310.      Before the request for an SPX connection is submitted, several ECBs are
  311.      already listening for data (this is important). SPX temporarily "steals"
  312.      two ECBs from the available and waiting ones for connection maintenance,
  313.      and then it puts the stolen ECBs back in the pool when finished. If there
  314.      are no pending ECBs for SPX to use, it cannot send an acknowledgement to
  315.      the remote site and the connection will stall and time out.
  316.  
  317.      *********************************************************
  318.      Figure 4: Establishing an SPX Connection [- C code example -]
  319.  
  320.      /* Start an SPX connection with the station at network
  321.         0x11111111, node 0x222222222222, socket 0x3333, use
  322.         local socket 0x4444 */
  323.  
  324.      #define NUM_BUFFS 5
  325.  
  326.      void call()
  327.      {
  328.        ECB send, receive[NUM_BUFFS], connect, term;
  329.        SPXHeader sendHdr, rcvHdr[NUM_BUFFS], connHdr;
  330.        char buffer[NUM_BUFFS][80], sendbuf[] = "Hello!";
  331.        int i, ccode, packetsReceived;
  332.        WORD spxConnectionID;
  333.  
  334.        for (i = 0; i < NUM_BUFFS; i++) {
  335.            receive[i].ESRAddress = NULL;
  336.            receive[i].socketNumber = 0x4444;
  337.            receive[i].fragmentCount = 2;
  338.            receive[i].fragmentDescriptor[0].address
  339.              = &(rcvHdr[i]);
  340.            receive[i].fragmentDescriptor[0].size
  341.              = sizeof(SPXHeader);
  342.            receive[i].fragmentDescriptor[1].address
  343.              = &(buffer[i]);
  344.            receive[i].fragmentDescriptor[1].size = 80;
  345.            SPXListenForSequencedPacket(receive[i]);
  346.        }
  347.  
  348.        connect.ESRAddress = NULL;
  349.        connect.socketNumber = 0x4444;
  350.        connect.fragmentCount = 1;
  351.        connect.fragmentDescriptor[0].address = &connHdr;
  352.        connect.fragmentDescriptor[0].size
  353.          = sizeof(SPXHeader);
  354.  
  355.        memset(connHdr.destination.network, 0x11, 4);
  356.        memset(connHdr.destination.node,    0x22, 6);
  357.        memset(connHdr.destination.socket,  0x33, 2);
  358.  
  359.        ccode = SPXEstablishConnection(0, 0,
  360.                                       &spxConnectionID,
  361.                                       &connect);
  362.        printf("SPXEstablishConnection return code
  363.               = 0x%x\n", ccode);
  364.        if (ccode != 0)
  365.            return;
  366.        while (connect.inUseFlag != 0)
  367.            IPXRelinquishControl();
  368.        if (connect.completionCode != 0)
  369.            return;
  370.        send.ESRAddress = NULL;
  371.        send.fragmentCount = 2;
  372.        send.fragmentDescriptor[0].address = &sendHdr;
  373.        send.fragmentDescriptor[0].size = sizeof(SPXHeader);
  374.        send.fragmentDescriptor[1].address = sendbuf;
  375.        send.fragmentDescriptor[1].size = 7;
  376.        SPXSendSequencedPacket(spxConnectionID, &send);
  377.  
  378.        packetsReceived = 0;
  379.        while (packetsReceived < 10) {
  380.            for (i = 0; i < NUM_BUFFS; i++) {
  381.                 if (receive[i].inUseFlag != 0) {
  382.                     if (receive[i].completionCode != 0) {
  383.                         packetsReceived = 10;
  384.                     /* If we get an error, terminate */
  385.                         break;
  386.                     }
  387.                     printf("Received: %s\n", buffer[i]);
  388.                     packetsReceived++;
  389.                 }
  390.                 SPXListenForSequencedPacket(receive[i]);
  391.            }
  392.            IPXRelinquishControl();
  393.        }
  394.  
  395.        term.ESRAddress = NULL;
  396.        term.fragmentCount = 1;
  397.        term.fragmentDescriptor[0].address = &connHdr;
  398.        term.fragmentDescriptor[0].size = sizeof(SPXHeader);
  399.        SPXTerminateConnection(spxConnectionID, &term);
  400.        while (term.inUseFlag != 0)
  401.            IPXRelinquishControl();
  402.        for (i = 0; i < NUM_BUFFS; i++)
  403.            IPXCancelEvent(receive[i]);
  404.      }
  405.      END of FIGURE 4
  406.      *********************************************************
  407.  
  408.  [-  *********************************************************
  409.      Figure 4a: Establishing an SPX Connection (Pascal example)
  410.  
  411.      { Start an SPX connection with the station at network
  412.        $11111111, node $222222222222, socket $3333, use
  413.        local socket $4444 }
  414.  
  415.      CONST NUM_BUFFS=5;
  416.  
  417.      Procedure call;
  418.      Var send,connect,term:Tecb;
  419.          receive          :array[1..NUM_BUFFS] of Tecb;
  420.          sendHdr, connHdr : TspxHeader;
  421.          rcvHdr           :array[1..NUM_BUFFS] of TspxHeader;
  422.          buffer           :array[1..NUM_BUFFS] of string[80];
  423.          sendBuf          :string;
  424.          i,packetsReceived:Integer;
  425.          spxConnectionId  :word;
  426.      begin;
  427.        sendbuf:="Hello!";
  428.  
  429.        for i:= 1 to NUM_BUFFS
  430.         do begin
  431.            receive[i].ESRAddress := NIL;
  432.            receive[i].socketNumber := $4444;
  433.            receive[i].fragmentCount = 2;
  434.            receive[i].fragment[1].address := @rcvHdr[i];
  435.            receive[i].fragment[1].size := sizeof(TSPXHeader);
  436.            receive[i].fragment[2].address := @buffer[i];
  437.            receive[i].fragment[2].size := 80;
  438.            SPXListenForSequencedPacket(receive[i]);
  439.            end;
  440.  
  441.        connect.ESRAddress := NIL;
  442.        connect.socketNumber := $4444;
  443.        connect.fragmentCount := 1;
  444.        connect.fragment[1].address := @connHdr;
  445.        connect.fragment[1].size := sizeof(TSPXHeader);
  446.  
  447.        FillChar(connHdr.destination.network, 4, $11);
  448.        FillChar(connHdr.destination.node,    6, $22);
  449.        FillChar(connHdr.destination.socket,  2, $33);
  450.  
  451.        IF NOT SPXEstablishConnection(0, 0,
  452.                                     spxConnectionID,
  453.                                     connect)
  454.          then begin
  455.               writeln('SPXEstablishConnection return code',
  456.                       HexStr(nwSpx.result,2));
  457.               exit;
  458.               end;
  459.  
  460.        while (connect.inUseFlag <> 0)
  461.         do IPXRelinquishControl();
  462.        if (connect.completionCode <> 0)
  463.         then exit;
  464.  
  465.        send.ESRAddress := NIL;
  466.        send.fragmentCount := 2;
  467.        send.fragment[1].address = @sendHdr;
  468.        send.fragment[1].size := sizeof(TSPXHeader);
  469.        send.fragment[2].address := @sendbuf[0];
  470.        send.fragment[2].size := ord(sendBuf[0])+1;
  471.        SPXSendSequencedPacket(spxConnectionID, send);
  472.  
  473.        packetsReceived := 0;
  474.        while (packetsReceived < 10)
  475.         do begin
  476.            for i :=1 to NUM_BUFFS
  477.             do begin
  478.                if (receive[i].inUseFlag <> 0)
  479.                   and (receive[i].completionCode <> 0)
  480.                 then begin
  481.                      packetsReceived := 10;
  482.                      exit;
  483.                      { If we get an error, terminate }
  484.                      end;
  485.                writeln('Received: ", buffer[i]);
  486.                inc(packetsReceived);
  487.                SPXListenForSequencedPacket(receive[i]);
  488.                end;
  489.            IPXRelinquishControl;
  490.            end;
  491.  
  492.        term.ESRAddress := NIL;
  493.        term.fragmentCount := 1;
  494.        term.fragment[1].address := @connHdr;
  495.        term.fragment[1].size := sizeof(TSPXHeader);
  496.        SPXTerminateConnection(spxConnectionID, term);
  497.        while (term.inUseFlag <> 0)
  498.         do IPXRelinquishControl;
  499.        for i:=1 to NUM_BUFFS
  500.         do IPXCancelEvent(receive[i]);
  501.      end;
  502.  
  503.      END of FIGURE 4a
  504.      ********************************************************* -]
  505.  
  506.      This process may sound complicated, but everything happens transparently.
  507.      As long as there are extra ECBs available, the application never knows
  508.      they have been borrowed, since SPX puts them back in the exact same state
  509.      they were in when they were pressed into service.
  510.  
  511.      If the connection is established with the SPX watchdog enabled, the
  512.      watchdog monitors the connection and notifies the application if the
  513.      connection fails, even if the application is not currently sending data
  514.      over the connection. This feature is useful for applications that start
  515.      SPX connections, but use them infrequently. For simplicity, however, the
  516.      example does not use the SPX watchdog.
  517.  
  518.      After the listen ECBs have been posted, the connection ECB is then set up
  519.      in much the same way the IPX send ECB was, except that this ECB has only
  520.      one fragment: the SPX header. The destination network, node, and socket
  521.      also are set the same way they were in the previous example.
  522.  
  523.      SPXEstablishConnection() is passed a retry count of zero, indicating that
  524.      you should use the default value for number of retries. This value is set
  525.      in the workstation's NET.CFG file using the IPX RETRY COUNT parameter,
  526.      which defaults to 20. The last zero passed in SPXEstablishConnection()
  527.      indicates not to use the SPX watchdog. The SPX connection ID is returned
  528.      as the third parameter. The SPX connection ID can be considered
  529.      equivalent to the NetBIOS local session number.
  530.  
  531.      Next, the sample code attempts to establish a connection. It polls the
  532.      ECB's in use flag waiting for the event to complete. The
  533.      IPXRelinquishControl() call is very important at this stage. If the code
  534.      did nothing but sit in a tight loop, IPX and SPX would never get the
  535.      chance to do any processing. IPXRelinquishControl() allows the IPX/SPX
  536.      layer to get some work done.
  537.  
  538.      Once the in use flag is set to zero, the example checks the return code
  539.      to see if the attempt to establish a connection was successful. The code
  540.      does not illustrate how to handle the various failure cases, but the most
  541.      likely cause of a failure would be that the other side is not yet
  542.      listening for a connection, just like in NetBIOS. After establishing the
  543.      connection, packets can be sent to the remote station.
  544.  
  545.      The SPXSendSequencedPacket() call requires much less information than its
  546.      IPX counterpart. Since the connection is already established, all
  547.      SPXSendSequencedPacket() needs is the SPX connection ID, an ESR address,
  548.      and the fragment information.
  549.  
  550.      After sending a packet, the example program waits for ten packets to
  551.      arrive. When an ECB comes back, the example displays the data and then
  552.      re-submits the ECB so that it can be used to receive a packet again.
  553.      After receiving ten packets, it issues an SPXTerminateConnection() call
  554.      to notify the other side that it is done.
  555.  
  556.      The call to terminate the connection takes almost the same parameters
  557.      that the establish connection call does, except that there is no need to
  558.      fill out any information in the SPX header. Once the connection has been
  559.      terminated, the pending listen ECBs must be cancelled. To do so, the
  560.      example calls IPXCancelEvent(). Unlike most other ECB-related calls,
  561.      IPXCancelEvent() does not return until the ECB has been cancelled so
  562.      there is no need to poll the in use flag.
  563.  
  564. Event Service Routines
  565.  
  566.      Event Service Routines (ESRs) serve the same purpose as the NetBIOS POST
  567.      routines, but require a little more setup than the standard POST routine.
  568.      Most ESRs are written in Assembly, although some call C functions.
  569.  
  570.      Figure 5 shows a generic ESR that calls a C function after allocating its
  571.      own stack. This is very important since the amount of free stack space
  572.      (if any) at interrupt time is unknown, and any attempt by a C function to
  573.      use the stack could result in memory corruption if the stack is
  574.      overflowed. The only way to guarantee that this will not occur is to
  575.      allocate sufficient stack space in the ESR.
  576.  
  577.      *********************************************************
  578.      Figure 5: Example Event Service Routine (ESR) [- C/ASM code -]
  579.  
  580.        .MODEL LARGE
  581.  
  582.        public       _ReceiveESRHandler
  583.        extrn        _ProcessReceiveData:PROC
  584.  
  585.        .DATA
  586.  
  587.      ; The stack segment and pointer must be saved so that you can set up
  588.      ; your own stack.
  589.  
  590.        stk_seg      dw 0                ; variable to store old stack segment
  591.        stk_ptr      dw 0                ; variable to store old stack pointer
  592.        stk_stk      dw 512 dup (0)      ; new stack of 1024 bytes in length
  593.        stk_end      dw 0                ; the end of the stack
  594.  
  595.        .CODE
  596.  
  597.      ; @datasize is TRUE if the model is MEDIUM or LARGE and FALSE if the
  598.      ; model is SMALL or COMPACT. Just modify the .MODEL ???? above for the
  599.      ; model you want. ES/SI holds the seg/offset of the currently used ECB
  600.      ; that ProcessReceivedData needs to process.
  601.  
  602.      _ReceiveESRHandler PROC far
  603.          mov        ax,DGroup
  604.          mov        ds,ax
  605.          mov        stk_seg,ss          ; Save the stack segment
  606.          mov        stk_ptr,sp          ; Save the stack pointer
  607.          mov        ss,ax               ; move the segment of new_stk into ss
  608.          mov        sp,offset stk_end   ; move offset of new_stk to sp
  609.      IF  @datasize
  610.          push       es                  ; push es if mem. model medium/large
  611.      ENDIF
  612.          push       si
  613.          call       _ProcessReceivedData
  614.          mov        ss,stk_seg          ; Restore old stack segment
  615.          mov        sp,stk_ptr          ; Restore old stack pointer
  616.          retf
  617.      _ReceiveESRHandler ENDP
  618.  
  619.          END END of FIGURE 5
  620.      *********************************************************
  621.  
  622.  
  623.  [-  *********************************************************
  624.      Figure 5a: Example Event Service Routine (ESR) (BASM/Pascal)
  625.  
  626.      { The stack segment and pointer must be saved so that you can set up
  627.        your own stack. }
  628.  
  629.       Var stk_stk:array[1..512] of word; { new stack of 1024 bytes in length }
  630.           stk_end:word;                  { the end of the stack              }
  631.  
  632.      {$F+}
  633.      Procedure ESRhandler(Var p:Tpecb);  { * Type TPecb=^Tecb }
  634.      begin
  635.      .
  636.      .
  637.      end;
  638.      {$F-}
  639.  
  640.      {$F+}
  641.      Procedure ListenESR; assembler;
  642.      asm { ES:SI are the only valid registers when entering this procedure ! }
  643.          mov dx, seg stk_stk { = seg @DATA }
  644.          mov ds, dx
  645.  
  646.          mov dx,ss  { setup of a new local stack }
  647.          mov bx,sp  { ss:sp copied to dx:bx}
  648.          mov ax,ds
  649.          mov ss,ax
  650.          mov sp,offset stk_end
  651.          push dx    { push old ss:sp on new stack }
  652.          push bx
  653.          push es    { * push es:si on stack as local vars }
  654.          push si    { * }
  655.          mov  di,sp { * }
  656.          push ss    { * push address of local ptr on stack }
  657.          push di    { * }
  658.  
  659.          CALL EsrHandler
  660.  
  661.          add sp,4   { skip stack ptr-copy }
  662.          pop bx     { restore ss:sp from new stack }
  663.          pop dx
  664.          mov sp,bx
  665.          mov ss,dx
  666.      end;
  667.      {$F-}
  668.  
  669.      Note that a local stack of 1024 bytes (512 words) may not be large
  670.      enough for some applications calling other functions within the
  671.      ESRhandler. Increase the stacksize by 1024 bytes at a time to
  672.      determine the stack requirement.
  673.  
  674.  
  675.          END END of FIGURE 5a
  676.      *********************************************************  -]
  677.  
  678.      Figure 6 contains a code fragment demonstrating the use of an ESR. It
  679.      receives ten SPX packets just like the example in Figure 3 does, but it
  680.      uses an ESR instead of polling the in use flag. The assembly language
  681.      routine from Figure 4 is declared as the ESR, and it in turn calls the
  682.      C [-/Pascal-] function ProcessReceivedData().
  683.  
  684.      *********************************************************
  685.      Figure 6: Using an Event Service Routine (ESR) [- C Code -]
  686.  
  687.      int packetCount = 0;
  688.  
  689.      void ProcessReceivedData(ECB *ecb)
  690.      {
  691.          packetCount++;
  692.          printf("%s\n", ecb->fragmentDescriptor[1].address);
  693.          SPXListenForSequencedPacket(ecb);   /* Re-issue the listen */
  694.      }
  695.  
  696.      main()
  697.      {
  698.          .
  699.          . /* This code is identical to SPX setup code in Fig. 4, except */
  700.          . /* for receive[i].ESRAddress line, which will be as follows: */
  701.  
  702.              receive[i].ESRAddress = (void (far *) () ) ReceiveESRHandler;
  703.  
  704.          .
  705.          . /* The send ECB does not normally use an ESR. */
  706.          .
  707.  
  708.          while (packetCount < 10)
  709.              IPXRelinquishControl();
  710.          .
  711.          . /* Shut down connection, cancel ECBs */
  712.          .
  713.      }
  714.  
  715.      END of FIGURE 6
  716.      *********************************************************
  717.  
  718.  [-  *********************************************************
  719.      Figure 6a: Using an Event Service Routine (ESR) (Pascal)
  720.  
  721.      Var PacketCount;
  722.  
  723.      Procedure ProcessReceivedData(Var ECB:Tecb)
  724.      begin
  725.          inc(packetCount);
  726.          writeln(string(ecb^.fragment[2].address^));
  727.          SPXListenForSequencedPacket(ecb);   { Re-issue the listen }
  728.      end;
  729.  
  730.      begin { main body }
  731.      PacketCount:=0;
  732.  
  733.          .
  734.          . { This code is identical to SPX setup code in Fig. 4a, except }
  735.          . { for receive[i].ESRAddress line, which will be as follows: }
  736.  
  737.              receive[i].ESRAddress := @ReceiveESRHandler;
  738.          .
  739.          . { The send ECB does not normally use an ESR. }
  740.          .
  741.  
  742.          while (packetCount < 10)
  743.             do IPXRelinquishControl;
  744.          .
  745.          . { Shut down connection, cancel ECBs }
  746.          .
  747.      end;
  748.  
  749.      END of FIGURE 6a
  750.      ********************************************************* -]
  751.  
  752.      IPX and SPX may look a little more complicated than NetBIOS at first, but
  753.      as soon as you begin using these protocols, you see how similar they
  754.      really are. Using IPX/SPX requires slightly more effort, but the
  755.      performance and compatibility gains when running under NetWare more than
  756.      compensate. If you are thinking about becoming more familiar with IPX and
  757.      SPX development, feel free to contact Novell's Developer Support group at
  758.      1-800-NETWARE (1-800-638-9273) or 1-801-429-5588.
  759.